#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/stddef.h>
#include <linux/ptrace.h>
#include <linux/ldt.h>
#include <linux/string.h>

void ret_from_sys_call(void) __asm__("ret_from_sys_call");

long last_pid = 0;

static int find_empty_process(void) {
    int free_task;
    int i;

repeat:
    if ((++last_pid) & 0xffff8000)
        last_pid = 1;
    i = NR_TASKS;
    while (--i > 0) {
        if (!task[i]) {
            free_task = i;
            continue;
        }
        if (task[i]->pid == last_pid || task[i]->pgrp == last_pid ||
            task[i]->session == last_pid)
            goto repeat;
    }
    return free_task;
}

int copy_mem(int nr, struct task_struct *p) {
    p->start_code = 0;
    set_base(p->ldt[1], 0); // 设置代码段基址
    set_limit(p->ldt[1], 0);
    set_base(p->ldt[2], 0); // 设置数据段基址
    set_limit(p->ldt[2], 0);
    if (copy_page_tables(p)) {
        printk("free_page_tables: from copy_mem\n");
        free_page_tables(p);
        return -ENOMEM;
    }
    return 0;
}

int sys_fork(struct pt_regs regs) {
    struct pt_regs *childregs;
    struct task_struct *p;
    int nr;

    p = (struct task_struct *)__get_free_page(GFP_KERNEL);
    if (!p)
        goto bad_fork;
    nr = find_empty_process();
    if (nr < 0)
        goto bad_fork_free;
    task[nr] = p;
    *p = *current;
    p->state = TASK_UNINTERRUPTIBLE;
    p->p_pptr = p->p_opptr = current;
    p->p_cptr = NULL;
    p->pid = last_pid;
    SET_LINKS(p);
    if (!(p->kernel_stack_page = __get_free_page(GFP_KERNEL)))
        goto bad_fork_cleanup;
    p->tss.es = KERNEL_DS;
    p->tss.cs = KERNEL_CS;
    p->tss.ss = KERNEL_DS;
    p->tss.ds = KERNEL_DS;
    p->tss.fs = USER_DS;
    p->tss.gs = KERNEL_DS;
    p->tss.ss0 = KERNEL_DS;
    p->tss.esp0 = p->kernel_stack_page + PAGE_SIZE;
    p->tss.tr = _TSS(nr);
    childregs = ((struct pt_regs *)(p->kernel_stack_page + PAGE_SIZE)) - 1;
    p->tss.esp = (unsigned long)childregs;
    p->tss.eip = (unsigned long)ret_from_sys_call;
    *childregs = regs;
    childregs->eax = 0;
    p->tss.back_link = 0;
    p->tss.eflags = regs.eflags & 0xffffcfff;
    p->tss.ldt = _LDT(nr);
    p->tss.bitmap = 0x8000;
    if (copy_mem(nr, p))
        goto bad_fork_cleanup;
    set_tss_desc(gdt + (nr << 1) + FIRST_TSS_ENTRY, &(p->tss));
    set_ldt_desc(gdt + (nr << 1) + FIRST_LDT_ENTRY, &(p->ldt));
    p->counter = current->counter >> 1;
    p->state = TASK_RUNNING;
    return p->pid;
bad_fork_cleanup:
    task[nr] = NULL;
    REMOVE_LINKS(p);
    free_page(p->kernel_stack_page);
bad_fork_free:
    free_page((long)p);
bad_fork:
    return -EAGAIN;
}
